/* -*- Arguments: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
/*
 * arguments.cc
 * Copyright (C) jurjen 2007 <jurjen@stwerff.xs4all.nl>
 * Part of the Moros Project http://moros.sourceforge.net/
 *
 * arguments.cc is free software.
 *
 * You may redistribute it and/or modify it under the terms of the
 * GNU General Public License, as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * moros.cc is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the COPYING file for more details.
 */

#define DEBUG_ROUTINE "arguments"

#include <iostream>
#include <map>
#include <vector>
#include "log.h"
#include "arguments.h"

using namespace std;

class argument {
  public:
    string text;    ///< Description of the parameter
    string param;   ///< Parameter to the argument
    string group;   ///< Grouping of parameters
    sigc::signal<void, std::string> handler; ///< Function to call when this argument is given
    argument() {}
};

map<string, argument> arguments; ///< Map from the arguments to its parameters
vector<string> help;             ///< --help text lines
vector<string> version;          ///< --version text lines

static string args="";      ///< List of parameters that are synonimes of each other
static string lastarg="";   ///< The previous argument found
static string lasttext="";  ///< The previous description
static argument last;       ///< The previous argument parameters
static string program;      ///< The name of the current program for --help and --version texts

void arg_add(std::string arg, std::string param, std::string group, std::string text, sigc::signal<void, std::string> handler) {
    if (args!="") {
        help.push_back("  "+args +"\t"+lasttext);
        args="";
        lasttext="";
    }
    dbg_calls("arg_add %s, %s, %s, %s", arg.c_str(), param.c_str(), group.c_str(), text.c_str());
    if (arg==ARG_PROGRAM) program=text;
    else if (arg==ARG_USAGE) help.push_back("Usage: "+program+" "+text);
    else if (arg==ARG_DESCR) {
        if (arg!=lastarg) help.push_back("");
        help.push_back(text);
    } else if (arg==ARG_EXAMPLE) help.push_back("Example: "+text);
    else if (arg==ARG_BUGS) help.push_back("\nReport bugs to <"+text+">");
    else if (arg==ARG_VERSION) version.push_back(program+" version "+text);
    else if (arg==ARG_COPYRIGHT) {
        if (arg!=lastarg) version.push_back("");
        version.push_back(text);
    }
    else if (arg==ARG_DATE) version.push_back("Release date: "+text);
    else {
        if (last.group!=group) {
            help.push_back("");
            if (group!="") help.push_back(group);
        }
        args=(arg.length()==1?"-":"--")+arg;
        lasttext=text;
        if (param!="" && arg.length()>1) {
            args+="="+param;
        }
    }
    lastarg=arg;
    last.text=text;
    last.param=param;
    last.group=group;
    last.handler=handler;
    if (arg!="" && arg.substr(0,1)!="=") {
        arguments[arg]=last;
    }
}

void arg_extra(std::string arg, std::string param) {
    dbg_calls("arg_extra %s, %s", arg.c_str(), param.c_str());
    const string comma=", ";
    args+=comma+(arg.length()==1?"-":"--")+arg;
    if (param!="" && arg.length()>1) {
        args+="="+param;
        last.param=param;
    }
    arguments[arg]=last;
}

/// Create a string that dupicates the base string a number of times

string dup_str(
    string todub, ///< The string to duplicate
    int dubs      ///< Number of duplicates needed
) {
    string res;
    int i;
    for (i=0; i<dubs; i++) res+=todub;
    return res;
}

/// Calculate the amount of room needed between arguments and descriptions and show the help

void show_help() {
    dbg_calls("show_help");
    unsigned int length=0;
    unsigned int i=0;
    for(i=0;i < help.size(); i++) {
        size_t pos=help.at(i).find("\t");
        if ( pos < string::npos && pos>length) {
            length=pos+1;
        }
    }
    for(i=0;i < help.size(); i++) {
        string line=help.at(i);
        size_t pos=line.find("\t");
        if ( pos < string::npos) {
            cout << line.substr(0, pos) << dup_str(" ", length-pos) << line.substr(pos+1) << endl;
        } else {
            cout << line << endl;
        }
    }
}

void arg_parse(int args, char *argv[]) {
    sigc::signal<void, std::string> dummy;
    arg_add("", "", "", "", dummy);
    string last="";
    int i;
    dbg_calls("arg_parse %d", args);
    for (i=1; i<args; i++) {
        string arg=argv[i];
        dbg_calls("argument %s", arg.c_str());
        if (arg=="--help") {
            show_help();
            exit(0);
        }
        if (arg=="--version") {
            unsigned int l;
            for(l=0;l < version.size(); l++) cout << version.at(l) << endl;
            exit(0);
        }
        if (last!="" && arg.length()>0 && arg.substr(0,1)!="-") { // parameter to the last argument
            arguments[last].handler.emit(arg);
            last="";
            continue;
        }
        if (arg.length()>2 && arg.substr(0,2)=="--") { // full name style argument
            size_t pos=arg.find("=");
            string carg=arg.substr(2, pos-2);
            dbg_error("Checking for argument '%s'", carg.c_str());
            if (arguments.count(carg)<1) {
                cout << program << ": unrecognized option '"+arg.substr(0,pos)+"'\n";
                cout << "Try '"<<program<<" --help' for more information\n";
                exit(1);
            }
            last="";
            if (pos < string::npos) {
                arguments[carg].handler.emit(arg.substr(pos+1));
            } else {
                dbg_calls("Remember argument '%s' for possible parameter", carg.c_str());
                last=carg;
            }
        } else if (arg.length()>1 && arg.substr(0,1)=="-") { // single letter style argument
            unsigned int pos;
            for (pos=1; pos<arg.length(); pos++) {
                if (arguments.count(arg.substr(pos,1))<1) {
                    cout << program << ": invalid option -- " << arg.substr(pos,1) << endl;
                    cout << "Try '"<<program<<" --help' for more information\n";
                    exit(1);
                }
                if (pos<arg.length()-1) {
                    arguments[arg.substr(pos,1)].handler.emit(arg.substr(pos,1));
                } else {
                    dbg_calls("Remember argument '%s' for possible parameter", arg.substr(pos).c_str());
                    last=arg.substr(pos);
                }
            }
        } else { // remaining arguments
            if (arguments.count("")<1) {
                cout << program << " invalid argument " << arg << endl;
                cout << "Try '"<<program<<" --help' for more information\n";
                exit(1);
            } else {
                arguments[""].handler.emit(arg);
            }
        }
    }
    if (last!="") arguments[last].handler(last);
}
